#!/usr/bin/env python2
'''
Created on Jul 1, 2011
TODO:
1. Load channel list from M3U file                DONE
2. Detect launch type by extension or marker      DONE
3. Add sopcast support                            DONE
4. Full-screen checkbox                           DONE
5. Volume control?
6. Tree control?
7. embed console output
8. asx parsing
9. open/save/edt playlist                         DONE
10. add Edit Source                               DONE
11. handle screensaver!!! xorg, etc.              DONE
12. on top
13. add 'open with system default engine' by extension
14. add 'open with default browser'               DONE 
15. Recent playlists submenu?
16. save settings #EXT..                          DONE
17, Enable custom command line via #EXT.. use human language
     combine both above to context EDIT menu on item context
18. Add search field

@author: xh
'''

from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.Qt import Qt
import subprocess
import time
import urllib
import codecs
import os
import sys
import webbrowser
import traceback

APPNAME = 'ChanChan'
DEFAULT_CHANNELS_FILE = '/home/xh/es.m3u'
MPLAYER_PATH = r'd:\pf\mplayer\mplayer.exe'
VLC_PATH = r'd:\pf\VideoLAN\VLC\vlc.exe'
EDITOR_SCITE_PATH = r'd:\pf\scite\SciTE.exe'
SOPCAST_SERVER_WAIT_SECS = 8
CACHE_SIZE_DEFAULT = '512'
IS_FULLSCREEN_DEFAULT = False
IS_ON_TOP_DEFAULT = False
GOOD_VALUES = ('true', 'yes', '1')
SOPCAST_LISTEN_PORT = '3889'
SOPCAST_SERVER_PORT = '37557'
SOPCAST_SERVER_URL = 'http://127.0.0.1:' + SOPCAST_SERVER_PORT
VALID_META_KEYS=('player','ontop','cache','playlist','fullscreen')

print('Python Version=' + sys.version)
is_win32 = sys.platform == "win32"

if is_win32:
    try:
        import ctypes
    except:
        print ('ctypes module not found! cannot manage screensaver')
        ctypes = None

EDITOR = is_win32 and (os.path.exists(EDITOR_SCITE_PATH) and EDITOR_SCITE_PATH or 'notepad') \
    or os.path.exists('/usr/bin/leafpad') and 'leafpad' \
    or os.path.exists('/usr/bin/mousepad') and 'mousepad' \
    or os.path.exists('/usr/bin/gvim') and 'gvim' \
    or os.getenv('EDITOR', 'vim')

window_id = None

imgdata_png_main = ('''iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz
AAALEgAACxIB0t1+/AAAABh0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzT7MfTgAABBF0RVh0
WE1MOmNvbS5hZG9iZS54bXAAPD94cGFja2V0IGJlZ2luPSIgICAiIGlkPSJXNU0wTXBDZWhpSHpy
ZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0
az0iQWRvYmUgWE1QIENvcmUgNC4xLWMwMzQgNDYuMjcyOTc2LCBTYXQgSmFuIDI3IDIwMDcgMjI6
Mzc6MzcgICAgICAgICI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcv
MTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFi
b3V0PSIiCiAgICAgICAgICAgIHhtbG5zOnhhcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4w
LyI+CiAgICAgICAgIDx4YXA6Q3JlYXRvclRvb2w+QWRvYmUgRmlyZXdvcmtzIENTMzwveGFwOkNy
ZWF0b3JUb29sPgogICAgICAgICA8eGFwOkNyZWF0ZURhdGU+MjAxMC0xMS0xOFQyMTozMTozOFo8
L3hhcDpDcmVhdGVEYXRlPgogICAgICAgICA8eGFwOk1vZGlmeURhdGU+MjAxMC0xMS0xOFQyMjow
OTo0MFo8L3hhcDpNb2RpZnlEYXRlPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgICAgPHJk
ZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6ZGM9Imh0dHA6Ly9w
dXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIj4KICAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9wbmc8
L2RjOmZvcm1hdD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94Onht
cG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgICAgICAgICAg18ZVGgAAAZRJREFUaIHtmTFLw0AYhp9UqAiCu5k6BbIKgpOT0En/nj9B
EJycFKRT/0ShILR0KiiUrnG4SwnxLsklJN8F7oEjpDnue95ckoMryBEDb0AGfAAzQRdnYmCFks/b
QlLIBZN8Bhz7KDZDTW+Gmu6443g2+Qx47zi2kUWpyIr2Iark1x3GreRoKNYmhIg8qGk1FXUJISaf
F19bijcJISpflGgTwgv5ooxLCK/ki1JNQngpn1MX4hYh+Rh4ATaW4j61jXY93YwE2Hsg5tr2QHoG
PAM31RPkJRfAdQT8AFfCMm05RKjpGDWm52sJpAPVT3U908v6CEz10faBMf44lHxOanB4KPW5N/Sx
BpCg7HBZuj419MkmQxo6cldzfsLXGfgG5vraXJ//c7V9haK+bQ20unE+P0KNCAF6ZAs8Aef6uLV1
9PUlDuuANGEdGJqwDoySEECaEECaEECaEEAaWwCJbZXW2Da2ku5ejUgwb2w1aqPfWpwAv9ISHThM
gC9piw58gnqBRvsHR54kBl6BnQdidW2nXWOAPxiZxPLFR8k8AAAAAElFTkSuQmCC'''
)

imgdata_png_clear = (
'''iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz
AAAFMQAABTEBt+0oUgAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAiDSURB
VGiB7Zl/bFXlGcc/z/ueW2gtFtrbiqMtCFMH4toptMg6SaNr8QcYUUlhYQTZjxhZICpt0WT1xxa6
GdEwdRtDQURwsMQl+0MSorW3DWiCa8wMLpIAqaA4K0WQ3nvPOfe8++Oee3ov97YINWlMeJKn73nP
fc97vt/nx/u8560YY/guixptACOVSwRGWy4RGG25RGC05TtPwPo2Jrl+9pyfKswOIDzSuUQEEUH5
rSgJ7vn6r/2d7yxMjR8xgaqamgUK2Q2MGQlolQ4yG3T6mAXpz46IQHVNzWKMbAdC44uKuHfR3Ywb
N+4bgxYAAUn+yWpBEL9/+vRp/vHGGziOkzHPRROomlW7HJGXAD1hwniWLV1KUdHlw4NOokIkBTRg
kAZ+EPhgC4WFhRQXF9P3Zd/ICVTNqnkAkRcAKSkuZvG99zAmL49YNBaMGRgY4OTJk0wqL0eJgAyC
GdLiBk6d6qeoqAitreT4wFOCVoKldQaWC16FqmfVPIzIi4CEw2HuvmshlmURi8cD7e/v571391NR
Pomuzk7idhzbtn11sB0bx++nWjsep+Ptt3Hicd566y1isSi24yR/91sRQZ9D4II8UF0z57dG5AmA
0nCY2xobAIhGo0GixWJR/vPBBzz15JNUVFRQGg7z9127mV1bgxI1pOXfe+9d7rvnHhoaGuju7mbL
1q3MratLWtz3glIqywPfmED17No/GGgGKCstpX7ezRhjiNtxhEHw/z14MAAP0NDQgAFe3/k6P5o1
C6WEILZFEGM4cOAATYsX09CQNEhdXR0AL2/ZQu2cOWitAwNdsAdERKpm1Ww0sEoEysrK+PFNczLA
p+L72CfHuH/FCiZPnpwxR2NDA8YYduzcSVV1NUopRARjDB/09LCkqSkAn5K6ujo+/PBD+vr6KC4p
BgSlFdq6gBwQEVVVU7NZlKzSWnHlxInMvvFGEgnPj2MHx3Fw/OvikmL+tnkzR48ezZprfmMjS5cs
oef997Ftm7ht8+8DB1jS1ERjY2PW+K6uLvbt309B4WU4jovrOmjJTmIZ6pOyvr7e+ioae1VEmkSE
stJSrpsxA6WUv6oQhE76ddyxOXbkCOvXr8/yBMCePXvY9uqrCLBs2TLmz5+fNSbS1cWmTZv4YXVV
Mnz8XDn40UdEo1Fee2WbpMbmDKGZM2fmjb28aJfW+i4RobQ0zNXTpuG6blBYUmGTUS0BJcKV5eW0
rltHew4S6YBzgo9E+PNf/8LM66/H8zw8zwTvUZKdxFkemDt3br6r9RuCNCoRwiUllJeXpyWfX+rJ
Bk9a37FtTnz2GX9sb8/piVwSiUR44cUXmX7dDJTSWQbq/aQX27Z5edPmwAMZOVBfX19orNCbltKN
ltaES0q44ooyHNdJrsWug+sm1+Rk7A+q7beur6IU4bJSmltb6O3tPS/4zkiEPz3/PNOuuRov4ZFw
HRIJFzfh4rpJVUplrUIZBBzDNq31PG1pisYXMaF4QvBwCpjjOME9x3UD0AnXJeG6OImkpl5YXFJC
c3PzsCQ6OzvZuHEjU78/FYyH67m4XgLXTZBwEyQSLomEiz5fHVAhS6fi2HUcTp8+Tf7YsajUOkzm
zpFzd45IWrymflcMRKN8fOgQlZWVOQmcHTiLweB5BmPcYG7l7z2SBnTxPG/4OjAGfu5p/aaI3GSM
YWBggGg0SigUYkwohBUKJVcFlQ04lwKcOP4pa9as5tZbbhnSA7ffdjvGM2x++SWmTpuKMQbP80gk
EnieF+SdtnRAKjB6emfv3r1fefF4g9L6Ha01lq8Yg+06RGNRBqJJUrFYjLgdD3IgCDVfbdvms2PH
WfvII9x6y61Dgk/JHXfcwcoV93Po40PEYjHchAsC2tJY1iCWYXMAoKOj4+vxBQW3W1rv0ZYmUP9h
7dcBAfAtlbJWSl3X4X8nTrCutZV58+Zlgd23bx+HDx/Oun/nnXfyq1/8kk+PHw/iPQU6XYclALBr
165oQV7eXdrS/0z3RK7JtPYt5KtSQn/fSZ5oezzY06RLd3c369vXs7Z57ZAkVq5YybHeY8GqE7wn
FRHnI+CTsL/64sv7LEu/ngXaygSdbpkvv+jjd089RW1tbdacXV1dPP3M01RMqSR8RSnNLc0cPpJN
YsGCBSxfvpzeo72ZnrA02sqEPOxeqKOjw5157fSfWVptyQA9hCfOnvmaNatXc8MNN+QE/8yGZ6ic
XEleKERBfj4TvzeRlpYWjhw5kjV+4YKF3PyTm4nFYmlhfJ46kEva2tq8ndt3rLSUfiFn+KRp0fgi
tm7Zyueff54xRyQSYcOGDUy+ajIhfyXTWpOfn8+kikm0rmvNItHT00NXd4TCcYVorbC0urAQShdj
jNm2ZesqZVlPD5kTlia/IJ/LLr+Mhx5+KCARiUR49rlnmTJtSgb41PMFBflUTqng0cfWBSR6enpo
b1/PD6Zfy5i8UJbH02XI3ehQ8usHH3hcoC39+EOds/7H43HOnDrDokWL2P7adq6adhVW2kdJrmMT
x7bpPfoJTU1L2P7adqbPuIZQKJSztrSsfTQoBhdMAODB1b9pVsIfcp7dKIVSCtu2GTh7lnA4jBWy
gt1kziLob5djsTin+vspLQtjWVYA+FwZMQGANQ+tWSVKNoqIKKUYO3ZsMjSURmkVEFEqCToFPvBW
1hkQgAm+1IwxGM/gGYMxHsYYXNfFGJNB4KLPhZ7b8Nzza1vWRhE2KRHleQny8/MJhSy00oMEtPKL
n0L8T0kJjlhS50SkgfZ80OnXHomEhzFOFo6L9kBKWh9rXaqUvCIiVta55jCa/L64OEn3wIhPp9t/
375DKb3Y0vrMN6nWWqsRgQf2pndG7IGU7N69WB88eF3oW5lseLHb2tq8VOdbIzBa8p3/B8clAqMt
lwiMtlwiMNryf3uGjtsJz/a4AAAAAElFTkSuQmCC'''
)

SOPCAST_INSTALL_HOWTO = '''
Ubuntu/Debian:
1. echo "deb http://ppa.launchpad.net/jason-scheunemann/ppa/ubuntu `lsb_release -cs` main" | sudo tee -a /etc/apt/sources.list && sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys CD30EE56
2. sudo apt-get update && sudo apt-get install sopcast-player
Archlinux:
    sudo pacman -S sopcast
'''

def getFirstUrl(playlist):
    lines = urllib.urlopen(str(playlist)).readlines()
    for line in lines:
        if not line.startswith('#'):
            return line

def config_win32_screensaver(toState):
    'http://miro.sourcearchive.com/documentation/2.5.4/windows-xul_2plat_2screensaver_8py-source.html'
    #needs ctypes on win32
    SPI_SETSCREENSAVEACTIVE = 17
    ctypes.windll.user32.SystemParametersInfoA(SPI_SETSCREENSAVEACTIVE, toState, None, 0)

def suspend_screensaver():
    if is_win32 and ctypes:
        config_win32_screensaver(0)
        return
    # get root windows id 
    global window_id
    os.sys
    window_id = subprocess.Popen('xwininfo -root | grep xwininfo | cut -d" " -f4', stdout=subprocess.PIPE, shell=True).stdout.read().strip()
    print ('xwininfo -root output', window_id)

    #run xdg-screensaver on root window
    xdg_output = subprocess.call(['xdg-screensaver', 'suspend', window_id])
    print('xdg-screen-suspend', xdg_output)

def resume_screensaver():
    if is_win32 and ctypes:
        config_win32_screensaver(1)
        return
    if window_id:
        xdg_output = subprocess.Popen('xdg-screensaver resume ' + window_id, stdout=subprocess.PIPE, shell=True).stdout.read().strip()
        print('xdg-screen-resume', window_id, xdg_output)

def get_icon_resource(hex_icon_data):
    data = hex_icon_data.decode("base64")
    pixmap = QPixmap()
    pixmap.loadFromData(data, 'PNG')
    return QIcon(pixmap)

class MediaItem (QListWidgetItem):
    num_items = 0
    def __init__(self, item_text, icon):
        QListWidgetItem.__init__(self)
        self.params = {}
        self.is_meta = False
        self.is_totem = False
        if item_text.startswith('#'):
            self.is_meta = True
            if item_text.startswith('#EXTINF'):
                item_text = item_text[11:]
            else:
                item_text = item_text[1:]
            self.parse_params(item_text)
            self.setFlags(self.flags() ^ Qt.ItemIsSelectable ^ Qt.ItemIsEnabled)
            self.setBackgroundColor(QColor('#CEE1B6'))
            self.setTextColor(QColor('#9F5700'))
        else:
            self.setIcon(icon)
            MediaItem.num_items += 1
        self.setText(item_text)
        self.setStatusTip(item_text)

    def parse_params(self, item_text):
        'read params like ;player=mplayer; ontop=yes into a dictionary object' 
        params = item_text.split(';')
#        print ('DBG:', params)
        if len(params):
            pairs = [param.strip().split('=', 1) for param in params if '=' in param]
            pairs = [(pair[0], pair[1]) for pair in pairs if len(pair) and (pair[0].lower() in VALID_META_KEYS)]

            if len(pairs):
                try:
                    self.params = dict(pairs)
                except Exception as e:
                    print e

class MyForm(QWidget):
    def __init__(self):
        QWidget.__init__(self)
        # setGeometry(x_pos, y_pos, width, height)
        self.setGeometry(100, 150, 500, 460)
        self.readSettings()
        self.setWindowTitle(APPNAME + " - " + self.chanells_path)
        self.icon_path = get_icon_resource(imgdata_png_main)
#        self.icon_path = os.path.join(os.path.dirname(sys.argv[0]), 'chanchan.ico')
        self.icon = QIcon(self.icon_path)
        self.setWindowIcon(self.icon)
        self.chanells = None
        self.chanells_all = None
        self.num_channels = 0

        # the player subprocess process
        self.proc = None
        self.proc_sopcast = None
        self.is_sopcast = False
        self.is_playlist = False
        self.on_top = False
        self.cache_size = '1000'

        # use a grid layout for the widgets
        grid = QGridLayout()

        # bind the button click to a function reference
        # new connect style, needs PyQt 4.5+
        ###btn_load.clicked.connect(self.load_channels_data)    

        btn_play = QPushButton("Play")
        btn_play.clicked.connect(self.play_media)
        btn_kill = QPushButton("Stop")
        btn_kill.clicked.connect(self.kill_proc)

        self.listbox = QListWidget()
        # new connect style, needs PyQt 4.5+
        self.listbox.clicked.connect(self.on_select)

        self.listbox.doubleClicked.connect(self.on_double_click)

        # attach right-click handler
        self.listbox.setContextMenuPolicy(Qt.ActionsContextMenu)
        #self.listbox.setContextMenuPolicy(Qt.CustomContextMenu)
        #http://talk.maemo.org/showthread.php?t=64034
        self.actionCopyUrl = QAction("Copy URL", self.listbox)
        self.connect(self.actionCopyUrl, SIGNAL("triggered()"), self.copy_to_clipboard)
        self.actionPlay = QAction("Play", self.listbox)
        self.actionRestartPlayer = QAction("Restart SopPlayer", self.listbox)
        self.actionReloadChannels = QAction("Reload List", self.listbox)
        self.actionEditChannels = QAction("Edit Playlist", self.listbox)
        self.actionOpenChannelsFile = QAction("Open Playlist File", self.listbox)
        self.actionEditSource = QAction("Edit Source", self.listbox)
        self.search = QLineEdit()
        
        self.connect(self.search, SIGNAL("textChanged(QString)"), self.on_search_text_change)
        
        # clear button
        self.clear_button = QToolButton()
        self.clear_button.setIcon(get_icon_resource(imgdata_png_clear))
        self.clear_button.setIconSize(QSize(16, 16))
        self.clear_button.setCursor(Qt.ArrowCursor)
        self.clear_button.setAutoRaise(True)
        self.clear_button.setEnabled(False)
#        self.main_layout.addWidget(self.clear_button)
        self.connect(self.clear_button, SIGNAL("clicked()"), self.clear_search_text)
        
        self.listbox.addAction(self.actionPlay)
        self.listbox.addAction(self.actionRestartPlayer)
        self.listbox.addAction(self.actionCopyUrl)
        self.listbox.addAction(self.actionOpenChannelsFile)
        self.listbox.addAction(self.actionReloadChannels)
        self.listbox.addAction(self.actionEditChannels)
        self.listbox.addAction(self.actionEditSource)

        self.connect(self.actionPlay, SIGNAL("triggered()"), self.on_double_click)
        self.connect(self.actionRestartPlayer, SIGNAL("triggered()"), self.restart_sopplayer)
        self.connect(self.actionReloadChannels, SIGNAL("triggered()"), lambda: self.load_channels_data(self.chanells_path))
        self.connect(self.actionEditChannels, SIGNAL("triggered()"), lambda: self.edit_file(str(self.chanells_path)))
        self.connect(self.actionOpenChannelsFile, SIGNAL("triggered()"), lambda: self.load_channels_data())
        self.connect(self.actionEditSource, SIGNAL("triggered()"), lambda: self.edit_file(path=sys.argv[0], editor=EDITOR))
#        self.listbox.connect(self.listbox, SIGNAL("customContextMenuRequested(QPoint)"), 
#                             self.on_right_click) 

#        self.txtChanInfo = QLineEdit()
#        self.txtChanInfo.setReadOnly(True)

#        self.logWindow = QTextEdit()
#        self.logWindow.setSizePolicyx(QSizePolicy.)    
        self.status = QLabel()
        self.status.setText('channels')

        self.groupBox = QGroupBox("Engine")
        sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.groupBox.sizePolicy().hasHeightForWidth())
        self.groupBox.setSizePolicy(sizePolicy)
#        self.groupBox.setAutoFillBackground(True)

        self.rbMplayer = QRadioButton('Mplayer', self.groupBox)
        self.rbMplayer.setChecked(True)
        self.rbGstreamer = QRadioButton('gst123', self.groupBox)
        self.rbVlc = QRadioButton('Vlc', self.groupBox)
        self.rbTotem = QRadioButton('Totem', self.groupBox)
        self.rbBrowser = QRadioButton('Browser', self.groupBox)

        self.hBoxTop = QHBoxLayout()
        self.hBoxTop.addWidget(self.rbMplayer)
        self.hBoxTop.addWidget(self.rbGstreamer)
        self.hBoxTop.addWidget(self.rbVlc)
        self.hBoxTop.addWidget(self.rbTotem)
        self.hBoxTop.addWidget(self.rbBrowser)
        self.groupBox.setLayout(self.hBoxTop)

        self.cbPlaylistFlag = QCheckBox('Playlist')
        self.cbPlaylistFlag.setToolTip('Resource is a M3U, ASX or PLS playlist')

        self.cbFullScreen = QCheckBox('Full Screen')
        self.cbFullScreen.setToolTip('Start video in full screen')
        self.cbFullScreen.setChecked(self.is_full_screen)
        self.cbInhibitScreensaver = QCheckBox('Inhibit Screensaver')
        self.cbInhibitScreensaver.setToolTip('Disable screensaver while playing stream')
        self.cbInhibitScreensaver.setChecked(self.is_inhibit_screen)
#        addWidget(widget, row, column, rowSpan, columnSpan)
        grid.addWidget(self.groupBox, 0, 0, 1, 3)
        grid.addWidget(btn_play, 0, 4, 1, 1)
        grid.addWidget(btn_kill, 0, 5, 1, 1)
        grid.addWidget(self.search, 1, 0, 1, 4)
        grid.addWidget(self.clear_button, 1, 3, 1, 1)
        grid.addWidget(self.status, 1, 5, 1, 1)
        # listbox spans over 5 rows and 2 columns
        grid.addWidget(self.listbox, 2, 0, 5, 6)
        ## BAD grid.addWidget(self.hBoxFlags, 6, 0, 1, 1)
        grid.addWidget(self.cbPlaylistFlag, 7, 0, 1, 1)
        grid.addWidget(self.cbFullScreen, 7, 1, 1, 1)
        grid.addWidget(self.cbInhibitScreensaver, 7, 2, 1, 1)
#        grid.addWidget(self.txtChanInfo, 7, 0, 1, 6)
#        grid.addWidget(self.logWindow, 8, 0, 1, 6)
        self.setLayout(grid)
        self.search.setFocus()
        self.load_channels_data(self.chanells_path)
        
    def clear_search_text(self):
        print '------clear-search-text---------'
        self.search.clear()
        
    def on_search_text_change(self):
        if not self.chanells_all:  # only need to do this once
            self.chanells_all = list(self.chanells)
        text = str(self.search.text()).strip()
        
        print ('DBG', len(text), len(self.chanells_all))
        
        if len(text) > 1:
            self.clear_button.setEnabled(True)
            filtered_list = self.get_matching_items(text.lower(), self.chanells_all)
            if len(filtered_list):
                self.chanells = filtered_list
            else:
                self.chanells = []
        else:
            self.chanells = list(self.chanells_all)
            self.clear_button.setEnabled(False)
        self.load_channels_data(None, False)        

    def get_matching_items(self, needle, haystack):
        matches = []
        found_in_meta = False
        last_meta_item = None
        
        for ch in haystack:
            is_meta = ch.startswith('#')
            if is_meta and not needle in ch.lower():
                last_meta_item = ch
            if needle in ch.lower():
                if is_meta:
                    found_in_meta = True
                elif not found_in_meta and last_meta_item not in matches:
                    matches.append(last_meta_item)
                matches.append(ch)
            elif found_in_meta:
                if not is_meta:
                    matches.append(ch)
                else:
                    found_in_meta = False
        return matches

    def closeEvent(self, event):

        quit_msg = "Are you sure you want to exit the program?"
        reply = QMessageBox.question(self, 'Message',
                                           quit_msg, QMessageBox.Yes, QMessageBox.No)

        if reply == QMessageBox.Yes:
            self.writeSettings()
            event.accept()
            QApplication.instance().quit()
        else:
            event.ignore()

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Escape:
            self.search.setFocus()
            self.search.selectAll()

    def copy_to_clipboard(self):
        clipboard = QApplication.clipboard()
        clipboard.setText(self.listbox.currentItem().text())

    def load_channels_data(self, new_path=None, read_from_file=True):
        MediaItem.num_items = 0
        if read_from_file:
            if not new_path:
                new_path = str(self.get_new_filename())
                if not new_path:
    #                QMessageBox.warning(self, APPNAME, 'No playlist selected')
                    return
    
            try:
                fh = codecs.open(new_path, 'r', 'utf8')
                self.chanells = [ch.strip() for ch in fh.readlines()]
            except Exception as e:
                print str(e)
                QMessageBox.warning(self, 'File not found', 'Error opening playlist -\n' + str(e))
                return
    
            self.chanells_path = new_path
            self.chanells = [ch.strip() for ch in self.chanells]
            self.chanells_all = None
#        self.chanells = [(ch.startswith('#EXTINF:-1,') and ch[11:] or ch) for ch in self.chanells]
        self.listbox.clear()
        current_params = None

        for chan in self.chanells:
            if not len(chan) or chan.strip() == '#EXTM3U':
                continue
            item = MediaItem(chan, self.icon)
            if item.is_meta:
                current_params = item.params
            elif current_params:
                item.params = current_params

            item.setStatusTip(chan)
            self.listbox.addItem(item)
#        self.listbox.addItems(self.chanells)
        self.setWindowTitle(APPNAME + ' - ' + self.chanells_path)
        self.status.setText(str(MediaItem.num_items) + ' channels')

    def edit_file(self, path, editor=EDITOR):
        subprocess.Popen([editor, path])

    def get_new_filename(self):
        return QFileDialog.getOpenFileName(self,
                       'Load Playlist file',
                       '',
                       "Playlist files (*.m3u);;All Files (*.*);")

    def on_quit(self):
        QApplication.instance().quit()

    def on_select(self):
        """an item in the listbox has been clicked/selected"""
        #current_channel = self.listbox.selectedItems()[0].text()
        current_item = self.listbox.currentItem()
        current_channel = str(current_item.text())

#        self.txtChanInfo.setText(current_channel)
        self.is_playlist = current_channel[-4:].lower() in ['.m3u', '.asx', '.pls']
        if current_channel.startswith('sop:'):
            self.rbMplayer.setChecked(True)
            self.cache_size = '512'
#        self.cbPlaylistFlag.setChecked(self.is_playlist)

        if current_item.params:
            myparams = current_item.params.keys()
            
            if 'player' in myparams:
                player = current_item.params['player'].lower()
                if player == 'totem':
                    self.rbTotem.setChecked(True)
                elif player == 'mplayer':
                    self.rbMplayer.setChecked(True)
                elif player == 'gst123':
                    self.rbGstreamer.setChecked(True)
                elif player == 'vlc':
                    self.rbVlc.setChecked(True)
                elif player == 'browser':
                    self.rbBrowser.setChecked(True)
                    
            if 'playlist' in myparams or 'pl' in myparams:  
                self.is_playlist = current_item.params['playlist'].lower() in GOOD_VALUES
            
            if 'fullscreen' in myparams or 'fs' in myparams: 
                self.is_full_screen = current_item.params['fullscreen'].lower() in GOOD_VALUES
            else:
                self.is_full_screen = IS_FULLSCREEN_DEFAULT
            
            if 'ontop' in myparams or 'top' in myparams: 
                self.on_top = current_item.params['top'].lower() in GOOD_VALUES
            else:
                self.on_top = IS_ON_TOP_DEFAULT
                    
            
            if 'cache' in myparams: 
                self.cache_size = current_item.params['cache']
            else:
                self.cache_size = CACHE_SIZE_DEFAULT
           
           
            self.cbPlaylistFlag.setChecked(self.is_playlist)
            self.cbFullScreen.setChecked(self.is_full_screen)
                
    def on_double_click(self):
        self.play_media()
        """an item in the listbox has been double-clicked"""
        
    def restart_sopplayer(self):
        current_item = self.listbox.currentItem()
        current_channel = str(current_item.text())
        if self.is_sopcast:
                    
            if is_win32:
                args = ['cmd', '/c', MPLAYER_PATH, '-cache', self.cache_size]
                fpopen = lambda : subprocess.Popen(args, creationflags=subprocess.CREATE_NEW_CONSOLE, cwd=os.path.dirname(sys.argv[0]))
            else:
                args = ['xterm', '-geometry', '45x8-20+150', '-e', 'mplayer', '-cache', self.cache_size]
                fpopen = lambda: subprocess.Popen(args) 
                           
            args.append(SOPCAST_SERVER_URL)
            print (args)
            self.proc = fpopen()

    def play_media(self, start_sopcast_server=True):
        current_item = self.listbox.currentItem()
        current_channel = str(current_item.text())

        if self.proc and self.proc.pid:
            self.kill_proc()
            time.sleep(1)

        args = []

        if self.cbInhibitScreensaver.isChecked():
            suspend_screensaver()

        # don't use xterm for vlc, totem
        if not is_win32 and (self.rbMplayer.isChecked() or self.rbGstreamer.isChecked()):
            args += ['xterm', '-geometry', '45x8-20+150', '-e']


        ################ SOPCAST #############
        self.is_sopcast = current_channel.lower().startswith('sop://')
        
        if self.is_sopcast:

            args_sopcast = ['xterm', '-geometry', '45x8-20+400', '-e', 'sopcast', current_channel, SOPCAST_LISTEN_PORT, SOPCAST_SERVER_PORT ]
            try:
                self.proc_sopcast = subprocess.Popen(args_sopcast)

            except Exception as e:
                error = str(e) + '\n\n' + traceback.format_exc()
                print(error)
                QMessageBox.warning(self, APPNAME,
"""ERROR! Sopcast executable not found:
%s
To install sopcast support on Linux, run:

%s""" % (error, SOPCAST_INSTALL_HOWTO))
                return

            current_channel = SOPCAST_SERVER_URL
            print ('Waiting for sopcast server starup at %s ...' % current_channel)
            time.sleep(SOPCAST_SERVER_WAIT_SECS)

        if self.rbMplayer.isChecked():
            
            if is_win32:
                args = ['cmd', '/c', MPLAYER_PATH, '-cache', self.cache_size]
            else:
                args += ['mplayer', '-cache', self.cache_size]
                
            self.on_top and args.append('-ontop')
            self.cbFullScreen.isChecked() and args.append('-fs')
            self.cbPlaylistFlag.isChecked() and args.append('-playlist')

        elif self.rbGstreamer.isChecked():
            args.append('gst123')
            if '.m3u' in current_channel:
                current_channel = getFirstUrl(current_channel)

        elif self.rbVlc.isChecked():
            if is_win32:
                args = [VLC_PATH]
            else:
                args = ['vlc']
            self.cbFullScreen.isChecked() and args.append('--fullscreen')
            args.append('--video-on-top')

        elif self.rbTotem.isChecked():
            args += ['totem', '--replace']
            # FIXME!!! totem segfaults when started with the --fullscreen switch
#            self.cbFullScreen.isChecked() and args.append('--fullscreen')

        elif self.rbBrowser.isChecked():
            webbrowser.open_new_tab(current_channel)
            return

        args.append(current_channel)

        print (args)

        try:
            if is_win32:
                self.proc = subprocess.Popen(args, creationflags=subprocess.CREATE_NEW_CONSOLE, cwd=os.path.dirname(sys.argv[0]))
            else:
                self.proc = subprocess.Popen(args, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
#                console_data = self.proc.stdout.read()
#                self.logWindow.setText(console_data)


        except Exception as e:
            error = str(e) + '\n\n' + traceback.format_exc()
            print(error)
            #traceback.print_exc(file=sys.stdout)
            QMessageBox.warning(self, '', "ERROR! Selected engine not available:\n%s" % error)

    def kill_proc(self):
        if self.cbInhibitScreensaver.isChecked():
            resume_screensaver()
        if self.proc:
            try:
                self.proc.kill()
            except:
                pass
        if self.is_sopcast and self.proc_sopcast:
            try:
                self.proc_sopcast.kill()
                os.system('killall sopcast')
            except:
                pass

    def readSettings(self):
        # store settings object
        self.settings = QSettings(QSettings.IniFormat, QSettings.UserScope, "xh", "chanchan")
        pos = self.settings.value("pos", QVariant(QPoint(200, 200))).toPoint()
        size = self.settings.value("size", QVariant(QSize(400, 400))).toSize()
        self.resize(size)
        self.move(pos)
        self.chanells_path = self.settings.contains('channels_file') and str(self.settings.value("channels_file").toString()) or DEFAULT_CHANNELS_FILE
        self.is_inhibit_screen = self.settings.contains('inhibit_screen') and self.settings.value("inhibit_screen").toBool()
        self.is_full_screen = self.settings.contains('fullscreen') and self.settings.value("fullscreen").toBool()

    def writeSettings(self):
        settings = QSettings(QSettings.IniFormat, QSettings.UserScope, "xh", "chanchan")
        settings.setValue("pos", QVariant(self.pos()))
        settings.setValue("size", QVariant(self.size()))
        settings.setValue("channels_file", QVariant(self.chanells_path))
        settings.setValue("inhibit_screen", QVariant(self.cbInhibitScreensaver.isChecked()))
        settings.setValue("fullscreen", QVariant(self.cbFullScreen.isChecked()))
        settings.sync()
#       self.setWindowTitle(current_channel)

app = QApplication([])
form = MyForm()
form.show()
app.exec_()

